local http_cookie = require "http_plugin.cookie"
local STRING_FIND = string.find
local STRING_GSUB = string.gsub
local STRING_SUB = string.sub
local TABLE_CONCAT = table.concat
local SPACE = " "
local COLON = ": "
local CTRL = "\r\n"

local http_parse = {}

local function trim(s)
    return (STRING_GSUB(s, "^%s*(.-)%s*$", "%1")) 
end

function http_parse.response_decode(data, res, req)
    res = res or {}
    local data_len = #data
    local clen = 0
    local p1 = 1
    local p2 = 1

    if res.header ~= nil then
        if res.header["Transfer-Encoding"] == "chunked" then
            local body_pos = STRING_FIND(data, "\r\n\r\n")
            if body_pos == nil then return 0, "data not enough" end
            p1 = body_pos + 4
            local tt = {}
            local i = 1
            local chunked_end = 0
            while true do
                p2 = STRING_FIND(data, "\r\n", p1)
                if p2 == nil then return 0, "data not enough" end
                clen = tonumber(STRING_SUB(data,  p1, p2 - 1), 16)
                if clen == 0 then
                    chunked_end = p2 + 3
                    res.content = TABLE_CONCAT(tt, "")
                    break
                else
                    p1 = p2 + 2
                    if data_len < p1 - 1 + clen then return 0, "data not enough" end
                    p2 = p1 +  clen
                    tt[i] = STRING_SUB(data, p1, p2 - 1)
                    i = i + 1
                    p1 = p2 + 2
                    if data_len < p1 then return 0, "data not enough" end
                end
            end
            res.chunk = tt
            return chunked_end
        else
            local body_pos = STRING_FIND(data, "\r\n\r\n")
            if body_pos == nil then return 0, "data not enough" end
            clen = tonumber(res.header["Content-Length"]) or 0
            if data_len == body_pos + 3 + clen  then
                res.content = STRING_SUB(data, body_pos + 4, clen)
                return body_pos + 3 + clen, res
            else
                return 0, "data not enough"
            end
        end
    end

    --p1 = 1
    p2 = STRING_FIND(data, SPACE)
	if p2 ~= nil then
		res.version = STRING_SUB(data, p1, p2-1)
		p1 = p2 + 1
	else 
		return -1
	end
    local p2 = STRING_FIND(data, SPACE, p1)
	if p2 ~= nil then
		res.status_code = tonumber(STRING_SUB(data, p1, p2-1))
		p1 = p2 + 1
	else 
		return -1
	end
    p2 = STRING_FIND(data, "\r\n", p1)
	if p2 ~= nil then
		res.status = STRING_SUB(data, p1, p2-1)
		p1 = p2 +2
	else 
		return -1
	end
    res.header = {}
    while STRING_SUB(data, p1, p1 + 1) ~= "\r\n" do
		p2 = STRING_FIND(data, ":", p1)
		if p2 == nil then break end
		local key = STRING_SUB(data, p1, p2-1)
		p1 = p2 + 1
		p2 = STRING_FIND(data, "\r\n", p1)
		if p2 == nil then return -1 end
		local val = trim(STRING_SUB(data, p1, p2-1))
		p1 = p2 + 2
        if key == "Set-Cookie" then
            http_cookie.set_cookie(val)
        end
        res.header[key] = val
	end
    if req and req.method == "HEAD" then
        return p1 + 1
    elseif res.header["Transfer-Encoding"] == "chunked" then
        p1 = p1 + 2
        local tt = {}
        local i = 1
        local chunked_end = 0
        while true do
            p2 = STRING_FIND(data, "\r\n", p1)
            if p2 == nil then return 0 end
            clen = tonumber(STRING_SUB(data,  p1, p2 - 1), 16)
            if clen == 0 then
                chunked_end = p2 + 3
                res.content = TABLE_CONCAT(tt, "")
                break
            else
                p1 = p2 + 2
                if data_len < p1 - 1 + clen then return 0 end
                p2 = STRING_FIND(data, "\r\n", p1)
                tt[i] = STRING_SUB(data, p1, p2 -1)
                i = i + 1
                p1 = p2 + 2
                if data_len < p1 then return 0 end
            end
        end
        res.chunk = tt
        return chunked_end
    else
        p1 = p1 + 1
        clen = tonumber(res.header["Content-Length"]) or 0
        if data_len < p1 + clen  then
            return 0
        end
        res.content = STRING_SUB(data, p1 + 1, clen)
        return p1 + clen
    end
end

function http_parse.response_encode(res)
    local tt = {}
    tt[1] = res.version
    tt[2] = SPACE
    tt[3] = res.status_code
    tt[4] = SPACE
    tt[5] = res.status
    tt[6] = CTRL
    local i = 7
    for k in pairs(res.header)  do
        if k ~= "Content-Length" then 
            tt[i] = k
            tt[i+1] = COLON
            tt[i+2] = res.header[k]
            tt[i+3] = CTRL
            i = i + 4
        end
    end
    if res.content then
        tt[i] = "Content-Lengtn"
        tt[i+1] = COLON
        tt[i+2] = #res.content
        tt[i+3] = CTRL
        tt[i+4] = CTRL
        tt[i+5] = res.content
    else
        tt[i] = CTRL
    end
    return TABLE_CONCAT(tt)
end

function http_parse.request_decode(data, req)
    req = req or {}
	local p1 = 1
	local p2 = 1
	p2 = STRING_FIND(data, SPACE)
	if p2 ~= nil then
		req.method = STRING_SUB(data, p1, p2-1)
		p1 = p2 + 1
	else 
		return -1, "decode method error"
	end
	p2 = STRING_FIND(data, SPACE, p1)
	if p2 ~= nil then
		req.uri = STRING_SUB(data, p1, p2-1)
        p1 = STRING_FIND(req.uri, "?", 1)
        if p1 ~= nil then
            req.qeury = STRING_SUB(req.uri, p1 + 1, p2)
            req.uri = STRING_SUB(req.uri, 1, p1 -1 )
        else
            req.qeury = ""
        end
		p1 = p2 + 1
	else 
		return -1, "decode uri error"
	end
	p2 = STRING_FIND(data, "\r\n", p1)
	if p2 ~= nil then
		req.version = STRING_SUB(data, p1, p2-1)
		p1 = p2 +2
	else 
		return -1, "decode version error"
	end
	while STRING_SUB(data, p1, p1 + 1) ~= "\r\n" do
		p2 = STRING_FIND(data, ":", p1)
		if p2 == nil then break end
		local key = STRING_SUB(data, p1, p2-1)
		p1 = p2 + 1
		p2 = STRING_FIND(data, "\r\n", p1)
		if p2 == nil then return -1, "decode header error:"..key end
		local val = trim(STRING_SUB(data, p1, p2-1))
		p1 = p2 + 2
        req.header[key] = val
	end
    local content_len = 0
    if req.header["Content-Length"] then
        content_len = tonumber(req.header["Content-Length"])
    end
	if content_len > 0 then
		req.content = STRING_SUB(data, p1 + 2, p1 + 1 + content_len)
	end
	return p1 + content_len + 1, req
end


function http_parse.request_encode(req)
    local cookie = http_cookie.get_cookie(req)
    if #cookie > 0 then
        req.header["Cookie"] = cookie
    end

    local tt = {}
    tt[1] = req.method
    tt[2] = SPACE
    if req.qeury and #req.qeury > 0 then
        tt[3] = req.uri.."?"..req.qeury
    else
        tt[3] = req.uri
    end
    tt[4] = SPACE
    tt[5] = req.version
    tt[6] = CTRL
    local i = 7
    for k in pairs(req.header)  do
        if k ~= "Content-Length" then 
            tt[i] = k
            tt[i+1] = COLON
            tt[i+2] = req.header[k]
            tt[i+3] = CTRL
            i = i + 4
        end
    end
    if req.content then
        tt[i] = "Content-Length"
        tt[i+1] = COLON
        tt[i+2] = #req.content
        tt[i+3] = CTRL
        tt[i+4] = CTRL
        tt[i+5] = req.content
    else
        tt[i] = CTRL
    end
    return TABLE_CONCAT(tt)
end

return  http_parse